<template>
  <div v-loading="loading" class="xt-table">
    <form-create v-model="mFApi" ref="form"
      v-if="form" :rule="form.rule" :option="formOption"
      class="p-l-10 p-t-10 b-b"
      @search-click="onSearch"
      @clear-click="onClear"
      v-on="$listeners">
    </form-create>
    <div class="btn-and-table">
      <div class="btn p-v-10" v-if="btn" data-flex="main:right cross:center" :class="{'b-b': !border}">
        <div data-flex-box="1" class="m-h-10 f-16 f-b">
          <slot name="title">{{title}}</slot>
        </div>
        <div class="btn-list" data-flex="cross:center">
          <template v-for="(b, index) in btn">
            <span :key="index" v-if="b.slot"><slot :name="b.slot"></slot></span>
            <os-button v-else v-permission="b.permission"
                size="small"
                v-bind="{type: b.type, icon: b.icon, text: b.text, loading: b.loading}"
                :key="index" @click="(cb) => onBtnClick(b, cb)"/>
          </template>
          <el-popover v-if="setting" placement="bottom-end" trigger="click" class="m-l-10" v-model="popoverShow">
            <os-draggable v-if="showFieldKeys" v-model="showFieldKeys" :data="selectorFields"></os-draggable>
            <el-button slot="reference" size="small" type="primary" plain icon="el-icon-setting"></el-button>
          </el-popover>
        </div>
      </div>
      <div class="slot-default"><slot></slot></div>
      <div class="table-warp">
        <el-table v-if="show" ref="table" :data="rows" v-bind="$attrs" :size="size" :class="{selected: !!selected}"
                  :max-height="mMaxHeight" :border="border"
                  :highlight-current-row="!multiple && !!selected"
                  @select="onSelect"
                  @select-all="onSelectAll"
                  @row-click="onRowClick"
                  @row-dblclick="onRowDbClick"
                  @sort-change="onSortChange"
                  @current-change="onCurrentChange"
                  :row-key="rowKey"
        >
          <template v-for="(column) in filteredColumns">
            <os-table-column :column="column" :rowKey="rowKey" :key="column.prop" :rows="rows"/>
          </template>
        </el-table>
        <div v-if="page" class="page p-v-10" data-flex="main:right">
          <el-pagination v-bind="mPage"
                         :current-page="pageNo"
                         :page-sizes="pageSizes"
                         :page-size="pageSize"
                         @current-change="onPageNumChange"
                         @size-change="onSizeChange"
                         :total="total">
          </el-pagination>
        </div>
      </div>
    </div>
  </div>
</template>
<script>
/**
 * 在A内嵌B子组件，B内嵌C子组件，要实现A与C通信，
 * 在B组件中C组件标签上使用v-bind="$attr"实现A传数据给C，C通过props接收，
 * B组件C组件标签上使用v-on="$listeners" 实现C同步数据给A，A组件有v-on:upRocket=""，在C通过this.$emit("upRocket");
 * 强制转换类型(!!)，它的结果会被强制转换成bool类型
 */
import OsTableColumn from './OsTableColumn'
// import formCreate from '@form-create/element-ui'
import { mapState } from 'vuex'
const domHeight = document.documentElement.clientHeight
export default {
  name: 'os-table',
  components: { OsTableColumn },
  props: {
    setting: { type: Boolean, default: true },
    fApi: Object, // 动态表单对象 formCreate
    form: Object, // 动态表单数据
    otherSearch: Object, // 自定义查询动作
    border: Boolean,
    title: String,
    btn: Array,
    showFields: Array, // 表格显示列
    columns: Array, // 表格行数据
    selected: [Object, Array], // 表示表格选中的行数据
    clickSelect: { type: Boolean, default: true },
    page: { type: [Object, Boolean], default: _ => ({}) }, // 是否分页
    rowKey: { type: [String, Function], default: 'id' }, // 表格隐藏行id
    pageNumField: String, // 分页页码
    autoHeight: { type: Boolean, default: false }, // 自动调整行高
    size: String, // 组件的尺寸 medium/small/mini
    maxHeight: [String, Number], // 行最大高度
    formSize: String,
    data: Array, // 表格数据
    value: Array, // 作为表单item使用
    action: String,
    autoLoad: { type: Boolean, default: true },
    clearSelectedAlert: Boolean,
    cacheKey: String,
    searchLabelWidth: { type: Number, default: 90 }
  },
  data () {
    return {
      loading: false,
      mFApi: this.fApi, // 动态表单对象
      showFieldKeys: [], // 选择表格是否显示列表值
      popoverShow: false, // 弹出窗体是否显示
      rows: [], // 表格行数据
      mData: this.data ? [...this.data] : null, // 复制一份data，作为本地数据
      total: 0, // 总记录数
      defaultPageSizes: [10, 20, 50, 100, 200, 300, 400],
      pageNo: this.page?.pageNo || 1, // 页码
      pageSize: this.page?.pageSize || 10, // 每页记录数
      formHeight: 0,
      sort: {},
      show: true, // 表格是否显示
      slotDefaultHeight: 0 // 插槽默认高度
    }
  },
  computed: {
    ...mapState({
      resources: state => state.resources
    }),
    mColumns () {
      const list = this.columns.map(item => ({ ...item, haveChildren: !!item.children }))
      const re = this.$utils.flatObject(list, 'children', 'parent', 'prop')
      this.handleFilters(re)
      return re
    },
    // small是否使用小型分页样式
    pageSmall () {
      return ['small', 'mini'].includes(this.size) // includes方法用来判断是否包含一个指定的值
    },
    // 分页设置
    mPage () {
      return {
        small: this.pageSmall,
        background: !this.pageSmall, // 是否为分页按钮添加背景色
        layout: this.page.layout || 'total, sizes, prev, pager, next, jumper', // 分页组件布局
        ...this.page
      }
    },
    // 动态表单设置
    formOption () {
      const { mounted, ...other } = this.form?.option || {}
      return {
        submitBtn: false,
        resetBtn: false,
        global: {
          '*': {
            props: {
              size: this.formSize || 'small',
              clearable: true
            }
          }
        },
        form: {
          inline: true,
          labelSuffix: ':',
          labelWidth: this.searchLabelWidth + 'px'
        },
        mounted: $f => {
          mounted && mounted($f)
        },
        ...other
      }
    },
    // 创建固定分页项数组
    pageSizes () {
      const pageSizes = this.page.pageSizes || this.defaultPageSizes
      const list = [...new Set([...pageSizes, this.pageSize])]
      list.sort((a, b) => a - b)
      return list
    },
    // 分页高度
    pageHeight () {
      return this.page ? (this.pageSmall ? 36 : 42) : 0
    },
    btnHeight () {
      return this.btn ? 49 : 0
    },
    slotHeight () {
      return this.slotDefaultHeight
    },
    // 表格高度,会出现滚动条
    mMaxHeight () {
      let maxHeight = (this.maxHeight || '') + ''
      if (maxHeight.includes('vh')) { // 1vh等于视口高度的1%
        maxHeight = (domHeight * Number(maxHeight.replace('vh', '')) / 100).toFixed() + 'px'
      }
      // dom高 - 顶部 - tabs - 内容padding - formHeight - pageHeight - 按钮 - 插槽高 - 修正值
      return this.autoHeight
        ? domHeight - 62 - 40 - this.formHeight - this.pageHeight - this.btnHeight - this.slotHeight - 6
        : (maxHeight || null)
    },
    // 判断选中行是否要高亮当前行
    multiple () {
      return this.columns.some(c => c.type === 'selection')
    },
    permissionFilteredColumns () {
      return this.mColumns.filter(item => {
        if (!item.permission) return true
        if (typeof item.permission === 'string' && this.resources.includes(item.permission)) {
          return true
        } else if (Array.isArray(item.permission)) {
          return item.permission.some(item => this.resources.includes(item))
        }
      })
    },
    // 表格显示列自定义列表数据集
    selectorFields () {
      return this.permissionFilteredColumns
        .filter(c => c.prop)
        .map(c => ({ key: c.prop, label: c.label }))
    },
    filteredColumns () {
      let list = this.permissionFilteredColumns.filter(c => !c.prop || this.showFieldKeys.includes(c.prop))
      // 排序
      list.sort((a, b) => {
        const index1 = this.showFieldKeys.indexOf(a.prop)
        const index2 = this.showFieldKeys.indexOf(b.prop)
        return index1 - index2
      })
      // 转成树
      list = this.$utils.list2Tree(list, undefined, 'prop', 'parent')
      return list
    },
    isAutoFilters () {
      return this.columns.some(c => c.filters)
    },
    mCacheKey () {
      const key = this.cacheKey || this.$parent?.$options?.name
      return ['dialog-content', 'ElTabPane', 'ElPopover'].includes(key) ? null : key
    }
  },
  watch: {
    // 监听列变化，改变showFieldKeys的值
    columns () {
      this.showFieldKeys = this.showFields || this.getColumns() // this.columns.map(item => item.prop).filter(item => item)
    },
    // 默认选中
    selected () {
      this.selectData()
    },
    showFieldKeys (val) {
      this.setCache()
      this.$emit('update:showFields', val)
    },
    value (val) {
      this.mData = [...val]
      this.getData()
    },
    data (val) {
      this.mData = [...val]
      this.getData()
    },
    filteredColumns () {
      this.reRender()
    },
    rows (val) {
      this.$emit('data', val)
      // 处理表头过滤
      this.handleFilters(this.mColumns)
    },
    mFApi (val) {
      this.$emit('update:fApi', val)
    }
  },
  /**
   * mounted 一般在初始化页面完成后，再对dom节点进行相关操作,不会承诺所有的子组件都一起被挂载
   * 通常是为 metheds 函数提前定义（ 类似提前声明变量 进入页面内容全部渲染完成后自动引函数）
   */
  mounted () {
    this.showFieldKeys = this.getCache()
    this.slotDefaultHeight = document.querySelector('.slot-default').clientHeight
    this.autoLoad && this.getData()
    this.formHeight = this.$refs.form ? (this.$refs.form.$el.clientHeight + 10) : 0
  },
  beforeDestroy () {
    this.mFApi && this.$f.resetFields(this.mFApi)
  },
  methods: {
    // 获取表格选中数据
    selectData () {
      if (!this.$refs?.table) return
      if (this.multiple) { // 表格多选
        this.$refs.table.clearSelection() // 用于多选表格，清空用户的选择
        this.$nextTick(() => {
          this.selected.forEach(s => {
            const row = this.rows.find(item => item[this.rowKey] === s[this.rowKey]) // 根据行ID得到选中行
            if (row) {
              this.$refs.table.toggleRowSelection(row, true) // 切换某一行的选中状态，第二个参数true，表示设置这一行选中
            }
          })
        })
      } else {
        if (this.selected && this.selected[this.rowKey]) {
          const row = this.rows.find(item => item[this.rowKey] === this.selected[this.rowKey])
          if (row) {
            this.$refs.table.setCurrentRow(row) // 用于单选表格，设定某一行为选中行
          } else {
            this.$refs.table.setCurrentRow() // 如果调用时不加参数，取消目前高亮行的选中状态
          }
        } else {
          this.$refs.table.setCurrentRow()
        }
      }
    },
    // 获取数据
    async getData () {
      if (this.mData) { // 本地数据
        if (this.page) { // 分页
          this.rows = this.mData.slice((this.pageNo - 1) * this.pageSize, this.pageNo * this.pageSize)
          this.total = this.mData.length // 总记录数
        } else {
          this.rows = this.mData // 不分页
        }
        setTimeout(() => {
          this.selectData() // 选中数据
        }, 0)
      } else if (this.action) { // url请求
        this.loading = true
        const { component, rows, formData, ...params } = this.getCbData()
        // $store.dispatch: 异步操作,存储,传值给vuex的mutation
        const param = { ...formData, ...params }
        const { data } = await this.$store.dispatch(this.action, param)
        this.loading = false
        if (data) {
          this.rows = data.rows || data.records || data.list
          this.total = data.total
          this.selectData() // 选中数据
        } else {
          this.total = 0
          this.rows = []
        }
      }
    },
    // 设置显示哪些列放入缓存
    setCache () {
      if (!this.showFields && this.setting && this.mCacheKey && this.btn) { // 设置表格显示字段
        const cacheMap = JSON.parse(localStorage.getItem('os-table-cache') || '{}')
        cacheMap[this.mCacheKey] = this.showFieldKeys
        localStorage.setItem('qx-table-cache', JSON.stringify(cacheMap))
      }
    },
    getCache () {
      if (!this.btn || !this.setting || !this.mCacheKey) {
        return this.getColumns()
      } else {
        if (!this.showFields) {
          const cacheMap = JSON.parse(localStorage.getItem('os-table-cache') || '{}')
          return cacheMap[this.mCacheKey] || this.getColumns()
        }
        return this.showFields
      }
    },
    getColumns () {
      const list = this.$utils.flatObject(this.mColumns, 'children', 'prop')
      // 如： [{ key: 1, value: 10 }，{ key: 3, value: 20 }]，map 返回结构['10s', '20s' ],filter返回结构[ { key: 1, value: '10ss' },{ key: 3, value: '20ss' }]
      return list.map(item => item.prop).filter(item => item)
    },
    // 当某一行双击获取的行数据
    // 拓展运算符（...）用于操作数组或对象,有两种层面,1.展开运算符,2.剩余操作符
    getCbData () {
      const { search, clear, ...formData } = this.form ? this.mFApi.formData() : {} // 表单数据
      const re = {
        formData: { ...formData, ...this.otherSearch },
        rows: this.rows,
        pageSize: this.pageSize,
        [this.pageNumField || 'pageNo']: this.pageNo,
        component: this
      }
      if (!this.$utils.isEmptyObject(this.sort)) {
        re.sort = this.sort
      }
      return re
    },
    // 处理表头过滤
    handleFilters (list) {
      if (this.isAutoFilters && this.rows) {
        list.forEach(item => {
          if (item.filters) {
            const texts = [...new Set(this.rows.map(row => row[item.prop]))] // [...set]，...将一个数组转为用逗号分隔的参数序列
            item.filters = texts.map(t => ({ text: t || '空', value: t }))
            item.filterMethod = item.filterMethod || ((value, row, column) => row[column.property] === value)
          }
        })
      }
    },
    reRender () {
      this.show = false
      this.$nextTick(() => (this.show = true))
    },
    renderParam (scope) {
      return {
        row: scope.row, column: scope.column, cellValue: scope.row[scope.column.property], index: scope.$index
      }
    },
    // 查询表单 查询按钮
    async onSearch () {
      // console.log('search')
      this.pageNo = 1
      // 搜索的时候清除selected
      if (this.clearSelectedAlert && this.selected) {
        if (this.multiple && this.selected.length > 0) {
          const re = await this.$utils.confirm('查询前是否清除已选中的数据？', '', {
            cancelButtonText: '不清除',
            confirmButtonText: '清除'
          })
          if (re) {
            this.selected.splice(0, this.selected.length) // splice() 方法向/从数组中添加/删除项目，然后返回被删除的项目
            this.$emit('update:selected', this.selected)
          }
        } else if (!this.multiple && !this.$utils.isEmptyObject(this.selected)) {
          for (const [key] of Object.entries(this.selected)) {
            delete this.selected[key]
          }
          this.$emit('update:selected', this.selected)
        }
      }
      this.getData()
    },
    // 查询表单 条件清除按钮
    onClear () {
      this.mFApi.resetFields()
      this.onSearch()
    },
    // 带权限按钮事件
    onBtnClick (b, cb) {
      if (b.cb) {
        b.cb(this.getCbData(), cb)
      }
    },
    // 表格单选事件
    onSelect (selection, row) {
      // 判断是选择还是取消选择， 看selection的最后一项是否是row即可
      const last = selection[selection.length - 1]
      const isSelect = last && last[this.rowKey] === row[this.rowKey]
      if (isSelect) {
        const list = this.$utils.mergeList(this.selected, selection, this.rowKey)
        this.selected.splice(0, this.selected.length, ...list) // fix 作为弹窗内容使用的时候，selected不能通过sync更新
        this.$emit('update:selected', list)
      } else {
        const index = this.selected.findIndex(item => item[this.rowKey] === row[this.rowKey])
        if (index > -1) {
          this.selected.splice(index, 1)
          this.$emit('update:selected', this.selected) // 更新父组件 selected 值
        }
      }
    },
    // 表格全选事件
    onSelectAll (selection) {
      if (selection.length === 0) {
        // 从选中列表中删除当前页数据
        this.rows.forEach(item => {
          const index = this.selected.findIndex(a => item[this.rowKey] === a[this.rowKey])
          this.selected.splice(index, 1)
        })
        this.$emit('update:selected', this.selected)
      } else {
        const list = this.$utils.mergeList(this.selected, selection, this.rowKey)
        this.selected.splice(0, this.selected.length, ...list) // fix 作为弹窗内容使用的时候，selected不能通过sync更新
        this.$emit('update:selected', list)
      }
    },
    // 当表格某一行被点击时会触发该事件
    onRowClick (row) {
      if (this.multiple && this.clickSelect) {
        const index = this.selected.findIndex((item) => item[this.rowKey] === row[this.rowKey])
        if (index >= 0) {
          this.selected.splice(index, 1)
        } else {
          this.selected.push(row)
        }
        this.$emit('update:selected', this.selected)
      }
    },
    // 当表格某一行被双击时会触发该事件
    onRowDbClick (row) {
      if (!this.multiple) {
        this.$emit('update:selected', row)
        this.$emit('row-dblclick', row)
      }
    },
    // 当表格的当前行发生变化的时候会触发该事件
    onCurrentChange (currentRow) {
      if (!this.multiple && currentRow) {
        this.$emit('update:selected', currentRow)
      }
    },
    // 分页，currentPage 改变时会触发
    onPageNumChange (pageNo) {
      this.pageNo = pageNo
      this.getData()
    },
    // 分页，pageSize 改变时会触发
    onSizeChange (pageSize) {
      this.pageNo = 1
      this.pageSize = pageSize
      this.getData()
    },
    // 当表格的排序条件发生变化的时候会触发该事件
    onSortChange ({ prop, order, column }) {
      // 注意：sortable为true的时候，el-table会自动排序，当只能排当前页数据,
      // 如果要排所有的数据，设置sortable为custom，并定义sortMethod
      // sortMethod返回数字，默认按照正序的计算方式
      if (column.sortable !== 'custom') return
      this.sort = { prop, order }
      if (this.mData) {
        if (order !== null) {
          const reverse = order === 'descending' ? -1 : 1
          this.mData.sort((a, b) => {
            let n = 0
            if (column.sortMethod) {
              n = column.sortMethod(a, b)
            } else {
              if (a.name > b.name) n = 1
              if (a.name < b.name) n = -1
            }
            return n * reverse
          })
        } else {
          this.mData = [...this.data]
        }
      }
      this.getData()
    }
  }
}
</script>

<style lang="scss" scoped>
  .xt-table{
    .btn-list{
      >*{
        margin-left: 10px;
      }
    }
    .el-form /deep/ {
      .el-form-item{
        margin-bottom: 10px;
      }
    }
    .page /deep/ {
      .el-select--mini{
        margin-top: -3px;
      }
    }
  }
</style>
